//
// Created by guoshunw on 2021/3/12.
//

#ifndef AVSTUDY_OWEOGGANALYZER_H
#define AVSTUDY_OWEOGGANALYZER_H

#include "OggAnalyzer.h"

enum class PayloadType {
    OPUS_ID_HEADER,
    OPUS_COMMENT_HEADER,
    PACKET_DATA,
};

struct __attribute__((__packed__)) OggPagePreHeader {
    // https://xiph.org/ogg/doc/rfc3533.txt
    char magic[4];
    uint8_t version;
    /**
     * header_type_flag: the bits in this 1 Byte field identify the
       specific type of this page.
      *  bit 0x01
         set: page contains data of a packet continued from the previous page
         unset: page contains a fresh packet
      *  bit 0x02
         set: this is the first page of a logical bitstream (bos)
         unset: this page is not a first page
      *  bit 0x04
         set: this is the last page of a logical bitstream (eos)
         unset: this page is not a last page
     **/
    uint8_t header_type;
    uint64_t granule_pos;
    uint32_t serial;
    uint32_t seq;
    uint32_t check_sum;
    uint8_t num_segments;
public:
    string toString() const;
};

struct OggPageHeader {
    static const size_t PRE_HEADER_SIZE = 27;
    static const uint32_t MAGIC = 0x5367674f; // OggS
    OggPagePreHeader preHeader;

    vector<size_t> segmentTable;

    size_t read(FILE *f);

    size_t getSize() const {
        return PRE_HEADER_SIZE + this->preHeader.num_segments;
    }

    size_t getSegmentsSize() const {
        size_t segmentsSize = 0;
        for (auto &size: segmentTable) {
            segmentsSize += size;
        }
        return segmentsSize;
    }

    bool isValid() {
        return MAGIC == *reinterpret_cast<uint32_t *>(preHeader.magic);
    }

    string toString() const;
};

struct __attribute__((__packed__)) OpusCommentHeader {
    char magic[8];
    // vendor string
    // 4-byte little-endian length field, followed by length bytes of UTF-8 vendor string.

    // TAG=value metadata strings (zero or more)
    // 4-byte little-endian string count.
    // Count strings consisting of 4-byte little-endian length and length bytes of UTF-8 string in "tag=value" form.
    void clear() {
        memset(this, 0, sizeof(*this));
    }

    string toString() const {
        stringstream os;
        os << "magic: ";
        for (auto c: magic) os << c;
        os << ", vendor: ";
        auto *vendor_str_len = reinterpret_cast<const uint32_t *>(((const char *) (this) + sizeof(magic)));
        const char *vendor_str = reinterpret_cast<const char *>(vendor_str_len + 1);
        for (std::size_t i = 0; i < *vendor_str_len; ++i) os << *vendor_str++;
        auto *tagCount = reinterpret_cast<const uint32_t * >(vendor_str);

        const char *tag = reinterpret_cast<const char *>(tagCount + 1);
        for (size_t i = 0; i < *tagCount; ++i) {
            os << ", tag" << i << ": ";
            auto *tagILen = reinterpret_cast<const uint32_t *>(tag);
            // skip to tag start
            tag += 4;
            for (size_t c = 0; c < *tagILen; ++c) os << *tag++;
        }
        os << endl;
        return os.str();
    };
};

// https://wiki.xiph.org/OggOpus
struct __attribute__((__packed__)) OpusIDHeader {
    char magic[8];
    uint8_t version;
    uint8_t channel;
    uint16_t pre_skip;
    uint32_t sample_rate;
    uint16_t output_gain;
    uint8_t channel_mapping_family;

    // optional channel mapping if channel_mapping_family>0
    void clear() {
        memset(this, 0, sizeof(*this));
    }

    string toString() const;
};



struct OggPage {
    OggPageHeader header;
    int pageIndex{};
    uint32_t starOffset{};

    const unsigned char *payload{nullptr};

    PayloadType getPayloadType() const {
        if (0 == memcmp(payload, "OpusHead", 8)) {
            return PayloadType::OPUS_ID_HEADER;
        }

        if (0 == memcmp(payload, "OpusTags", 8)) {
            return PayloadType::OPUS_COMMENT_HEADER;
        }
        return PayloadType::PACKET_DATA;
    }

    const OpusIDHeader *getPayloadAsOpusIDHeader() const {
        return reinterpret_cast<const OpusIDHeader *>(payload);
    }

    const OpusCommentHeader *getPayloadAsOpusCommentHeader() const {
        return reinterpret_cast<const OpusCommentHeader *>(payload);
    }

    string payloadToString(size_t line_limit = 16, size_t max_out = 160) const;

    size_t getSize() const {
        return this->header.getSize() + header.getSegmentsSize();
    }

    size_t getPayloadOffset() const {
        return starOffset + header.getSize();
    }

    size_t getPayloadSize() const {
        return header.getSegmentsSize();
    }

    int read(FILE *f);

    string toString() const;

    virtual ~OggPage() {
        delete payload;
    }
};


class OweOggAnalyzer : public OggAnalyzer{
public:
    explicit OweOggAnalyzer(const char *file);

    int parse() override;

    ~OweOggAnalyzer() override;
    static int parsingOpusSampleRate;

private:
    FILE* parsingFile;

};


#endif //AVSTUDY_OWEOGGANALYZER_H
